package com.jinke.api.modules.app.config;

import com.jinke.api.modules.app.util.IdWorkerUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;

/**
 * 发送消息
 *
 * @author: vinfant
 * @time: 2023/4/27 20:07
 */
@Service
@Slf4j
public class SendCodeManager {



    @Resource
    private RedisTemplate<String, String> redisTemplate;

    private static final String CONFIG_PRE =  "msg:send:";
    private static final String INTERVAL_KEY = CONFIG_PRE + "{}:interval";
    private static final String VALIDITY_KEY = CONFIG_PRE + "{}:validity";
    private static final String TOKEN_KEY = CONFIG_PRE + "token:{}";
    private static final String SPILT = ",";
    //时间间隔值，单位可能是秒
    private static final Integer INTERVAL = 60;
    //验证码5分钟内有效
    private static final Integer VALIDITY_TIME = 300;

    /**
     * 添加发送频率控制
     *
     * @param toSend
     */
    public void addInterValCtrl(String toSend, String code) {
        int interval = INTERVAL;
        redisTemplate.opsForValue().set(getIntervalPath(toSend), code, Duration.of(interval, ChronoUnit.SECONDS));
    }

    /**
     * 添加验证码有效期控制
     *
     * @param toSend
     */
    public void addValidityCtrl(String toSend, String code) {
        int validityTime = VALIDITY_TIME;
        String key = getValidityPath(toSend);
        if (redisTemplate.hasKey(key)) {
            redisTemplate.delete(key);
        }

        //设置错误次数
        String value = code + SPILT + 3;
        redisTemplate.opsForValue().set(key, value, Duration.of(validityTime, ChronoUnit.SECONDS));
    }

    /**
     * 是否可以发送，频率控制
     *
     * @param toSend
     * @return
     */
    public String addCanSendCtrl(String toSend) {
        try {
            if (redisTemplate.hasKey(getIntervalPath(toSend))) {
                return String.format("发送异常，%d秒内只能发送一次", INTERVAL);
            }
        }catch (Exception e){
            log.error("校验短信次数处出错"+e.getMessage());
            return null;
        }

        return null;
    }

    /**
     * 匹配验证码
     *
     * @param toSend
     * @param code
     * @return
     */
    public MatchResult doMatchCode(String toSend, String code) {
        String cacheCode = getCode(toSend);
        if (StringUtils.isEmpty(cacheCode)) {
            return new MatchResult(false, "验证码失效，请重新发送");
        }

        if (!cacheCode.equals(code)) {
            int errTime = getErrTime(toSend);
            if (errTime != -1) {
                if (0 >= errTime - 1) {
                    //删除验证码
                    doReset(toSend);
                    return new MatchResult(false, String.format("验证码错误次数达到%d，请重新发送", 3));
                } else {
                    doReduceErrTime(toSend);
                }
            }
        } else {
            doReset(toSend);
            String token = addMatchToken(toSend, code);
            return new MatchResult(true, "验证通过", token);
        }

        return new MatchResult(false, "验证码错误，请重新输入");
    }

    /**
     * 匹配token
     *
     * @param toSend
     */
    public MatchResult doMatchToken(String toSend, String token) {
        String key = getTokenPath(token);
        if (!redisTemplate.hasKey(key)) {
            return new MatchResult(false, "请先验证验证码");
        }

        if (!toSend.equals(redisTemplate.opsForValue().get(key))) {
            return new MatchResult(false, "请先验证验证码");
        }

        //移除
        redisTemplate.delete(key);

        return new MatchResult(true, "验证成功");
    }

    /**
     * 匹配验证成功后添加tocken
     *
     * @param toSend
     */
    public String addMatchToken(String toSend, String code) {
        String token = IdWorkerUtil.getToken() + code;
        String key = getTokenPath(token);
        redisTemplate.opsForValue().set(key, toSend, Duration.of(VALIDITY_TIME, ChronoUnit.SECONDS));
        return token;
    }

    /**
     * 减少次数
     *
     * @param toSend
     */
    public void doReduceErrTime(String toSend) {
        //错误次数
        int errTime = getErrTime(toSend) - 1;
        String key = getValidityPath(toSend);
        //时间
        int expireTime = this.redisTemplate.getExpire(key).intValue();
        String value = this.redisTemplate.opsForValue().get(key);
        value = value.substring(0, value.indexOf(SPILT)) + SPILT + errTime;
        redisTemplate.opsForValue().set(key, value);
        redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
    }

    /**
     * 重置
     *
     * @param toSend
     */
    private void doReset(String toSend) {
        redisTemplate.delete(getIntervalPath(toSend));
        redisTemplate.delete(getValidityPath(toSend));
    }


    /**
     * 获取错误次数
     *
     * @param toSend
     * @return
     */
    public int getErrTime(String toSend) {
        String value = redisTemplate.opsForValue().get(getValidityPath(toSend));
        int splitAt = value.indexOf(SPILT);
        String err = value.substring(splitAt + 1);
        if (StringUtils.isEmpty(err)) {
            return -1;
        }
        return Integer.parseInt(err);
    }

    /**
     * 获取验证码
     *
     * @param toSend
     * @return
     */
    public String getCode(String toSend) {
        String value = redisTemplate.opsForValue().get(getValidityPath(toSend));
        if (StringUtils.isEmpty(value)) {
            return null;
        }
        int splitAt = value.indexOf(SPILT);
        return value.substring(0, splitAt);
    }

    /**
     * 获取间隔控制路径
     *
     * @param toSend
     * @return
     */
    private String getIntervalPath(String toSend) {
        return doReplace(INTERVAL_KEY, toSend);
    }


    private String getValidityPath(String toSend) {
        return doReplace(VALIDITY_KEY, toSend);
    }

    private String getTokenPath(String token) {
        return doReplace(TOKEN_KEY, token);
    }

    private String doReplace(String path, String toSend) {
        return path.replace("{}", toSend);
    }

    /**
     * 匹配结果
     */
   public class MatchResult {
        private boolean isMatch;
        private String reason;
        private String token;

        public MatchResult(boolean isMatch, String reason) {
            this.isMatch = isMatch;
            this.reason = reason;
        }

        public MatchResult(boolean isMatch, String reason, String token) {
            this.isMatch = isMatch;
            this.reason = reason;
            this.token = token;
        }

        public boolean isMatch() {
            return isMatch;
        }

        public void setMatch(boolean match) {
            isMatch = match;
        }

        public String getReason() {
            return reason;
        }

        public void setReason(String reason) {
            this.reason = reason;
        }

        public String getToken() {
            return token;
        }

        public void setToken(String token) {
            this.token = token;
        }
    }
}
